package simpledb.optimizer;

import simpledb.common.Database;
import simpledb.common.DbException;
import simpledb.common.Type;
import simpledb.execution.Predicate;
import simpledb.execution.SeqScan;
import simpledb.storage.*;
import simpledb.transaction.Transaction;
import simpledb.transaction.TransactionAbortedException;
import simpledb.transaction.TransactionId;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * TableStats represents statistics (e.g., histograms) about base tables in a
 * query. 
 * 
 * This class is not needed in implementing lab1 and lab2.
 */
public class TableStats {

    private static final ConcurrentMap<String, TableStats> statsMap = new ConcurrentHashMap<>();

    static final int IOCOSTPERPAGE = 1000;

    public static TableStats getTableStats(String tablename) {
        return statsMap.get(tablename);
    }

    public static void setTableStats(String tablename, TableStats stats) {
        statsMap.put(tablename, stats);
    }

    // statsMap字段是私有静态成员
    // 通过反射机制绕过编译期的访问控制和常量限制，强行修改其值
    public static void setStatsMap(Map<String,TableStats> s)
    {
        try {
            java.lang.reflect.Field statsMapF = TableStats.class.getDeclaredField("statsMap");
            statsMapF.setAccessible(true);
            statsMapF.set(null, s);
        } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException | SecurityException e) {
            e.printStackTrace();
        }

    }

    public static Map<String, TableStats> getStatsMap() {
        return statsMap;
    }

    public static void computeStatistics() {
        Iterator<Integer> tableIt = Database.getCatalog().tableIdIterator();

        System.out.println("Computing table stats.");
        while (tableIt.hasNext()) {
            int tableid = tableIt.next();
            TableStats s = new TableStats(tableid, IOCOSTPERPAGE);
            setTableStats(Database.getCatalog().getTableName(tableid), s);
        }
        System.out.println("Done.");
    }

    /**
     * Number of bins for the histogram. Feel free to increase this value over
     * 100, though our tests assume that you have at least 100 bins in your
     * histograms.
     */
    static final int NUM_HIST_BINS = 100;

    // 表id
    private final int tableid;
    // 表对应的文件
    private final DbFile file;
    private TupleDesc tupleDesc;
    // 读取每一页的io成本
    private final int ioCostPerPage;
    // 表的tuple个数
    private int count;
    // int字段对应的统计信息（key为index）
    private final Map<Integer, IntHistogram> hMap;
    // string字段对应的统计信息（key为index）
    private final Map<Integer, StringHistogram> sMap;

    private void generateStats() {
        tupleDesc = this.file.getTupleDesc();
        int numFields = tupleDesc.numFields();
        TransactionId tid = new TransactionId();
        SeqScan scan = new SeqScan(tid, this.tableid);

        // 为每个字段初始化统计信息容器（一次扫描收集所有字段数据）
        List<List<Field>> fieldValues = new ArrayList<>(); // 每个索引对应一个字段的所有值
        int[] imins = new int[numFields]; // 每个整数字段的最小值
        int[] imaxs = new int[numFields]; // 每个整数字段的最大值
        boolean[] isIntField = new boolean[numFields]; // 标记字段是否为整数类型

        // 初始化容器
        for (int i = 0; i < numFields; i++) {
            fieldValues.add(new ArrayList<>());
            isIntField[i] = tupleDesc.getFieldType(i) == Type.INT_TYPE;
            if (isIntField[i]) {
                imins[i] = Integer.MAX_VALUE;
                imaxs[i] = Integer.MIN_VALUE;
            }
        }

        try {
            scan.open(); // 显式打开扫描器（部分SimpleDB版本需要）
            // 一次全表扫描，收集所有字段的数据
            while (scan.hasNext()) {
                Tuple tuple = scan.next();
                for (int i = 0; i < numFields; i++) {
                    Field field = tuple.getField(i);
                    fieldValues.get(i).add(field); // 收集当前字段的值

                    // 处理整数字段的min/max
                    if (isIntField[i]) {
                        int value = ((IntField) field).getValue();
                        imins[i] = Math.min(imins[i], value);
                        imaxs[i] = Math.max(imaxs[i], value);
                    }
                }
            }
            this.count = fieldValues.get(0).size(); // 总元组数（所有字段的size相同）
        } catch (Exception e) {
            throw new RuntimeException("扫描表时出错: " + e.getMessage(), e); // 保留原始异常信息
        } finally {
            // 关闭事务，释放资源（关键！）
            Database.getBufferPool().transactionComplete(tid);
            scan.close(); // 关闭扫描器
        }

        // 为每个字段创建直方图
        for (int i = 0; i < numFields; i++) {
            List<Field> fields = fieldValues.get(i);
            if (isIntField[i]) {
                // 处理空表场景：避免imin > imax
                int min = (count == 0) ? 0 : imins[i];
                int max = (count == 0) ? 0 : imaxs[i];
                IntHistogram histogram = new IntHistogram(NUM_HIST_BINS, min, max);
                for (Field field : fields) {
                    histogram.addValue(((IntField) field).getValue());
                }
                hMap.put(i, histogram);
            } else if (tupleDesc.getFieldType(i) == Type.STRING_TYPE) { // 显式判断字符串类型
                StringHistogram histogram = new StringHistogram(NUM_HIST_BINS);
                for (Field field : fields) {
                    histogram.addValue(((StringField) field).getValue());
                }
                sMap.put(i, histogram);
            }
            // 可扩展：处理其他类型字段
        }
    }

    public TableStats(int tableid, int ioCostPerPage) {
        this.tableid = tableid;
        this.file = Database.getCatalog().getDatabaseFile(tableid);
        this.ioCostPerPage = ioCostPerPage;
        this.count = 0;

        this.hMap = new HashMap<>(); // 单线程场景下普通HashMap足够
        this.sMap = new HashMap<>();
        generateStats();
    }

    /**
     * Estimates the cost of sequentially scanning the file, given that the cost
     * to read a page is costPerPageIO. You can assume that there are no seeks
     * and that no pages are in the buffer pool.
     * 
     * Also, assume that your hard drive can only read entire pages at once, so
     * if the last page of the table only has one tuple on it, it's just as
     * expensive to read as a full page. (Most real hard drives can't
     * efficiently address regions smaller than a page at a time.)
     * 
     * @return The estimated cost of scanning the table.
     */
    public double estimateScanCost() {
        // some code goes here
        return ((HeapFile) file).numPages() * ioCostPerPage;
    }

    /**
     * This method returns the number of tuples in the relation, given that a
     * predicate with selectivity selectivityFactor is applied.
     * 
     * @param selectivityFactor
     *            The selectivity of any predicates over the table
     * @return The estimated cardinality of the scan with the specified
     *         selectivityFactor
     *         估算过滤之后能剩多少，selectivityFactor代表过滤之后剩的百分比（0.3）
     *         表的总元组数量 × 选择性系数
     */
    public int estimateTableCardinality(double selectivityFactor) {
        // some code goes here
        return (int) Math.round(count * selectivityFactor);
    }

    /**
     * The average selectivity of the field under op.
     * @param field
     *        the index of the field
     * @param op
     *        the operator in the predicate
     * The semantic of the method is that, given the table, and then given a
     * tuple, of which we do not know the value of the field, return the
     * expected selectivity. You may estimate this value from the histograms.
     * 只有equals操作符可以调用这个，所以要实现equals的对应规则。
     * */
    public double avgSelectivity(int field, Predicate.Op op) {
        // some code goes here
        if (tupleDesc.getFieldType(field)==Type.INT_TYPE) {
            IntHistogram histogram = this.hMap.get(field);
            return histogram.avgSelectivity();
        }else {
            StringHistogram histogram = this.sMap.get(field);
            return histogram.avgSelectivity();
        }
    }

    /**
     * Estimate the selectivity of predicate <tt>field op constant</tt> on the
     * table.
     * 
     * @param field
     *            The field over which the predicate ranges
     * @param op
     *            The logical operation in the predicate
     * @param constant
     *            The value against which the field is compared
     * @return The estimated selectivity (fraction of tuples that satisfy) the
     *         predicate
     */
    public double estimateSelectivity(int field, Predicate.Op op, Field constant) {
        // some code goes here
        if (tupleDesc.getFieldType(field)==Type.INT_TYPE) {
            IntHistogram histogram = this.hMap.get(field);
            return histogram.estimateSelectivity(op, ((IntField)constant).getValue());
        }else {
            StringHistogram histogram = this.sMap.get(field);
            return histogram.estimateSelectivity(op, ((StringField)constant).getValue());
        }
    }

    /**
     * return the total number of tuples in this table
     * */
    public int totalTuples() {
        // some code goes here
        return count;
    }

}
